#include "proportional.hpp"
#include <cmath>

void Proportional::setPlotStartWidth(const wxCoord plotStartWidth) { 
	this->plotStartWidthInitial = plotStartWidth;
}

void Proportional::setPlotStartHeight(const wxCoord plotStartHeight) { 
	this->plotStartHeightInitial = plotStartHeight;
}

void Proportional::setPlotEndWidth(const wxCoord plotEndWidth) { 
	this->plotEndWidthInitial = plotEndWidth;
}

void Proportional::setPlotEndHeight(const wxCoord plotEndHeight) { 
	this->plotEndHeightInitial = plotEndHeight;
}

void Proportional:: setFontSize(const unsigned int fontSize) { 
	this->fontSize = fontSize; 
}

void Proportional::setTicks(const unsigned int xTicks, const unsigned int yTicks) {
	this->xTicks = xTicks;
	this->yTicks = yTicks;
}

void Proportional::gridOn(const bool useGrid) { 
	this->useGrid = useGrid; 
}

void Proportional::setTitle(const wxString& title) {
	this->title = title; 
}

void Proportional::setXlabel(const wxString& xLabel) {
	this->xLabel = xLabel; 
}

void Proportional::setYlabel(const wxString& yLabel) { 
	this->yLabel = yLabel;
}

void Proportional::legendOn(const bool useLegend) { 
	this->useLegend = useLegend; 
}

void Proportional::setLegend(const std::vector<wxString>& legend, const PLACEMENT legendPosition) { 
	this->legend = legend; 
	this->legendPosition = legendPosition;
}

void Proportional::setData(const std::vector<std::vector<double>>& data2D) { 
	findMaxMin2Ddata(data2D, minX, maxX, minY, maxY); 
}

void Proportional::setData(const std::vector<double>& data1D) {
	minX = 0;
	maxX = data1D.size();
	findMaxMin1Ddata(data1D, minY, maxY);
}

void Proportional::setWxPlotType(const WXPLOT_TYPE wxPlotType) { 
	this->wxPlotType = wxPlotType; 
}

void Proportional::setYlim(const double minY, const double maxY) {
	this->minY = minY; 
	this->maxY = maxY; 
}

void Proportional::setXlim(const double min, const double max) {
	this->minX = min;
	this->maxX = max;
}

wxCoord Proportional::getPlotStartWidth() const {
	return plotStartWidthComputed; 
}

wxCoord Proportional::getPlotStartHeight() const { 
	return plotStartHeightComputed; 
}

wxCoord Proportional::getPlotEndWidth() const { 
	return plotEndWidthComputed; 
}

wxCoord Proportional::getPlotEndHeight() const { 
	return plotEndHeightComputed; 
}

void Proportional::drawFigure(wxDC& dc) {

	// Set the font, or get the font size
	if (fontSize) {
		dc.SetFont(wxFont(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
	}
	else {
		fontSize = dc.GetFont().GetPointSize();
	}

	// Set default size on the rectangle
	wxCoord yStartRectangle = plotStartHeightInitial;
	wxCoord xStartRectangle = plotStartWidthInitial;
	wxCoord heightRectangle = plotEndHeightInitial;
	wxCoord widthRectangle = plotEndWidthInitial;

	// Set color
	setColourPen(dc, PLOT_COLOUR_BLACK);

	// Compute the width and height of the ticks
	wxCoord ticksWidth = 0, ticksHeight = 0;
	if (this->xTicks > 0 || this->yTicks > 0) {
		char value[100];
		std::snprintf(value, sizeof(value), "%0.2f", maxY > std::abs(minY) ? maxY : minY); // Sometimes a negative number can be longer than a positive number
		dc.GetTextExtent(value, &ticksWidth, &ticksHeight);
	}

	// Compute width and height of the title
	wxCoord titleWidth = 0, titleHeight = 0;
	if (title.length() > 0) {
		dc.GetTextExtent(title, &titleWidth, &titleHeight);
	}

	// Compute width and height of the X-label
	wxCoord xLabelWidth = 0, xLabelHeight = 0;
	if (xLabel.length() > 0) {
		dc.GetTextExtent(xLabel, &xLabelWidth, &xLabelHeight);
	}

	// Compute width and height of the Y-label
	wxCoord yLabelWidth = 0, yLabelHeight = 0;
	if (yLabel.length() > 0) {
		dc.GetTextExtent(yLabel, &yLabelWidth, &yLabelHeight);
	}

	// Write out in X-axis - Add some 5 and 10 to make a proper distance between rectangle and numbers
	wxCoord x = plotEndWidthInitial / 2 - titleWidth / 2;                                       // Compute the centre of the title
	wxCoord y = 0;                                                                              // Compute the hight placement of the title
	dc.DrawText(title, x, y);                                                                   // Draw title
	yStartRectangle += titleHeight + 5;                                                         // Move down the start of the rectangle
	heightRectangle -= titleHeight;                                                             // Decrease the height of the rectangle
	heightRectangle -= xLabelHeight + 10;                                                       // Make room for ticks and X-label						
	if (xTicks > 0) {
		heightRectangle -= ticksHeight;
	}
	x = plotEndWidthInitial / 2 - xLabelWidth / 2;                                              // Compute the centre of the X-label
	y = heightRectangle + (titleHeight > 0) * xLabelHeight;                                     // Compute the hight placement of the X-label
	if (xTicks > 0 && ticksHeight > 0) {
		y += ticksHeight;
	}
	dc.DrawText(xLabel, x, y + 5);                                                              // Draw X-label

	// Write out in Y-axis - Add some 5 and 10 to make a proper distance between rectangle and numbers
	x = 0;                                                                                      // Compute the width placement of the Y-label
	y = plotEndHeightInitial / 2 + yLabelWidth / 2;                                             // Compute the centre of the title
	dc.DrawRotatedText(yLabel, x, y, 90);                                                       // Draw Y-label
	xStartRectangle += yLabelHeight + 5;                                                        // Strafe left the start of the rectangle
	widthRectangle  -= yLabelHeight + 10;                                                       // Decrease the width of the rectangle
	if (yTicks > 0) {
		xStartRectangle += ticksWidth;
		widthRectangle -= ticksWidth;
	}

	// Update the size
	plotStartHeightComputed = yStartRectangle;
	plotStartWidthComputed = xStartRectangle;
	plotEndHeightComputed = heightRectangle + yStartRectangle;
	plotEndWidthComputed = widthRectangle + xStartRectangle;

	// Draw rectangle frame
	dc.DrawRectangle(xStartRectangle, yStartRectangle, widthRectangle, heightRectangle);
}

void Proportional::drawTicks(wxDC& dc) {
	if (this->xTicks > 0 || this->yTicks > 0) {
		const wxCoord yStartRectangle = plotStartHeightComputed;
		const wxCoord xStartRectangle = plotStartWidthComputed;
		const wxCoord heightRectangle = plotEndHeightComputed - plotStartHeightComputed;
		const wxCoord widthRectangle = plotEndWidthComputed - plotStartWidthComputed;

		// Set color - Black
		wxPen pen(plotColours.at(PLOT_COLOUR_BLACK));
		dc.SetPen(pen);

		// Vertical ticks
		if (this->xTicks > 1) {
			wxRect rctLastDraw{};
			// Counter
			unsigned int counter = 0;
			// Compute steps
			const auto stepsX = static_cast<double>(widthRectangle) / (this->xTicks - 1);
			// Compute scalars
			const double scalarX = (maxX - minX) / (this->xTicks - 1);
			wxCoord textWidth, textHeight;
			wxCoord digitWidth = 0, digitHeight = 0;
			dc.GetTextExtent(L"0", &digitWidth, &digitHeight);
			char value[100]{};
			for (double x = xStartRectangle; x < xStartRectangle + widthRectangle - 1; x += stepsX) {
				// Always skip last tick
				wxRect rct{};
				std::snprintf(value, sizeof(value), "%0.2f", minX + scalarX * counter);
				dc.GetTextExtent(value, &textWidth, &textHeight);
				rct.x = static_cast<int>(std::round(x - static_cast<double>(textWidth) / 2));
				rct.y = yStartRectangle + heightRectangle;
				rct.width = textWidth;
				rct.height = textHeight;
				// Skip if overlapped with the previous text
				if (!rct.Intersects(rctLastDraw)) {
					dc.DrawText(value, rct.GetLeftTop());
					rctLastDraw = rct;
					rctLastDraw.Inflate(digitWidth, 0);
				}
				counter++;
			}
			if (counter < this->xTicks) {
				// Patch last tick
				std::snprintf(value, sizeof(value), "%0.2f", minX + scalarX * counter);
				dc.GetTextExtent(value, &textWidth, &textHeight);
				dc.DrawText(value, xStartRectangle + widthRectangle - textWidth + 5, yStartRectangle + heightRectangle);
			}
		}

		// Horizontal ticks
		if (this->yTicks > 1) {
			// Counter
			unsigned int counter = 0;
			// Compute steps
			const auto stepsY = static_cast<double>(heightRectangle) / (this->yTicks - 1);
			// Compute scalars
			const double scalarY = (minY - maxY) / (this->yTicks - 1);
			char value[100]{};
			wxCoord textWidth, textHeight;
			for (double y = yStartRectangle; y <= yStartRectangle + heightRectangle; y += stepsY) {
				std::snprintf(value, sizeof(value), "%0.2f", maxY + scalarY * counter);
				dc.GetTextExtent(value, &textWidth, &textHeight);
				// 2 is only for make a proper distance from rectangle
				dc.DrawText(
					value, 
					xStartRectangle - textWidth - 2, 
					std::round(std::max(y - textHeight, double(0)))
				);  
				counter++;
			}
			if (counter < this->yTicks) {
				std::snprintf(value, sizeof(value), "%0.2f", maxY + scalarY * counter);
				dc.GetTextExtent(value, &textWidth, &textHeight);
				// 2 is only for make a proper distance from rectangle
				dc.DrawText(value, xStartRectangle - textWidth - 2, yStartRectangle + heightRectangle - textHeight); 
			}
		}
	}
}

void Proportional::drawGrid(wxDC& dc) {
	if (useGrid && (this->xTicks > 0 || this->yTicks > 0)) {
		const wxCoord yStartRectangle = plotStartHeightComputed;
		const wxCoord xStartRectangle = plotStartWidthComputed;
		const wxCoord heightRectangle = plotEndHeightComputed - plotStartHeightComputed;
		const wxCoord widthRectangle = plotEndWidthComputed - plotStartWidthComputed;

		// Set color - Light Gray
		wxPen pen(plotColours.at(PLOT_COLOUR_LIGHT_GRAY));
		dc.SetPen(pen);

		if (xTicks > 1) {
			// Compute steps
			const auto stepsX = static_cast<double>(widthRectangle) / (this->xTicks - 1);
			// Vertical lines
			for (double x = xStartRectangle + stepsX; x <= xStartRectangle + widthRectangle; x += stepsX) {
				auto rx = std::round(x);
				dc.DrawLine(rx, yStartRectangle, rx, yStartRectangle + heightRectangle);
			}
		}

		if (yTicks > 1) {
			const auto stepsY = static_cast<double>(heightRectangle) / (yTicks-1);
			// Horizontal lines
			for (double y = yStartRectangle + stepsY; y <= yStartRectangle + heightRectangle; y += stepsY) {
				auto ry = std::round(y);
				dc.DrawLine(xStartRectangle, ry, xStartRectangle + widthRectangle, ry);
			}
		}

		// Draw rectangle frame
		dc.SetPen(wxPen(plotColours.at(PLOT_COLOUR_BLACK), 1, wxPENSTYLE_SOLID));
		dc.SetBrush(wxBrush(plotColours.at(PLOT_COLOUR_WHITE), wxBRUSHSTYLE_TRANSPARENT));
		dc.DrawRectangle(xStartRectangle, yStartRectangle, widthRectangle, heightRectangle);
	}
}

void Proportional::drawLegend(wxDC& dc) {
	if (useLegend) {
		const wxCoord yStartRectangle = plotStartHeightComputed;
		const wxCoord xStartRectangle = plotStartWidthComputed;
		const wxCoord heightRectangle = plotEndHeightComputed - plotStartHeightComputed;
		const wxCoord widthRectangle = plotEndWidthComputed - plotStartWidthComputed;

		// Get the size of the legend
		const size_t legendSize = legend.size();

		// Get the largest text width
		wxCoord textWidth, textHeight;
		wxCoord largestTextWidth = 0, largestTextHeight = 0;
		for (size_t i = 0; i < legendSize; i++) {
			dc.GetTextExtent(legend.at(i), &textWidth, &textHeight);
			if (i == 0) {
				largestTextWidth = textWidth;
				largestTextHeight = textHeight;
			}
			else {
				if (textWidth > largestTextWidth) {
					largestTextWidth = textWidth;
				}
				if (textHeight > largestTextHeight) {
					largestTextHeight = textHeight;
				}
			}
		}

		// Compute the coordinates where to place the legend - Add 15, 10, 5 and 2.5 to make a proper distance between rectangle and numbers
		wxCoord x = 0, y = 0;
		switch (legendPosition) {
		case PLACEMENT_NORTH_WEST:
			x = plotStartWidthComputed + 5;
			y = plotStartHeightComputed + 5;
			break;
		case PLACEMENT_NORTH_EAST:
			x = plotEndWidthComputed - largestTextWidth - 15;
			y = plotStartHeightComputed + 5;
			break;
		case PLACEMENT_SOUTH_WEST:
			x = plotStartWidthComputed + 5;
			y = plotEndHeightComputed - largestTextHeight * legendSize - 10;
			break;
		case PLACEMENT_SOUTH_EAST:
			x = plotEndWidthComputed - largestTextWidth - 15;
			y = plotEndHeightComputed - largestTextHeight * legendSize - 10;
			break;
		case PLACEMENT_CENTRE:
			x = widthRectangle / 2 + xStartRectangle - largestTextWidth / 2 - 5;
			y = heightRectangle / 2 + yStartRectangle - largestTextHeight * legendSize / 2 - 5 / 2;
			break;
		default:
			break;
		}

		// The legend should be a white box
		setColourBrush(dc, PLOT_COLOUR_WHITE);

		// Draw the legend now
		dc.DrawRoundedRectangle(x, y, largestTextWidth + 10, largestTextHeight * legendSize + 5, 2);
		for (size_t i = 0; i < legendSize; i++) {
			setColourText(dc, i, PLOT_COLOUR_WHITE);
			dc.DrawText(legend.at(i), x + 5, y + largestTextHeight * i);
		}

		// Restore to default
		setColourText(dc, PLOT_COLOUR_BLACK, PLOT_COLOUR_WHITE);
	}
}